/**
 * @method 深拷贝：自己实现一个深拷贝函数
 * @param {Object} originObj  被拷贝的对象
 * @param {Object} targetObj  可选，拷贝后返回的目标对象
 * @returns newObejct
 */
function deepClone(originObj, targetObj) {
  if (typeof originObj !== 'object') {
    // 原始值拷贝情况直接返回
    return originObj;
  }
  // 处理可能为空的情况
  var tar = targetObj || {};
  var toStr = Object.prototype.toString;
  var arrayType = '[object Array]';

  for (var key in originObj) {
    // console.log(key);
    // 只处理当前对象上的属性
    if (originObj.hasOwnProperty(key)) {
      // 判断当前类型是否为对象类型，如果为对象类型且其值不为null，继续拷贝
      if (typeof originObj[key] === 'object' && originObj[key] !== null) {
        // 通过 Object.prototype.toString 方法判断其为对象 or 数组
        tar[key] = toStr.call(originObj[key]) === arrayType ? [] : {};
        deepClone(originObj[key], tar[key]);
      } else {
        // 原始值数据直接赋值即可
        tar[key] = originObj[key];
      }
    }
  }

  // todo: 最后别忘了把拷贝后的目标返回
  return tar;
}

/**
 * @method 重写（forEach）
 * @param {Function} callback 可传三个参数，从左到右分别是，当前元素、下标、当前遍历数组
 * @param {any}      thisArg  可选，callback 函数执行是 this 的值
 * @returns undefined
 */
// 直接在 Array 的原型上挂载一个方法即可
Array.prototype.myforEach = function (callback) {
  // 保存当前 this 指向，函数调用的话一般 this 指向为当前调用者，数组调用就指向这个数组本身
  // console.log(this);
  var _thisArr = this;
  // 通过 arguments 获取并保存第二个参数，如果传递了就通过 call() 改变并调用函数，否则默认指向 window
  var _thisArg = arguments[1] || window;
  // 保存数组长度用于遍历
  var _len = _thisArr.length;

  for (var i = 0; i < _len; i++) {
    callback.call(_thisArg, _thisArr[i], i, _thisArr);
  }
  // 返回值 undefined，不需要处理
}

/**
 * @method 重写（map）遍历数组元素
 * @param   {Function} callback 可传三个参数，从左到右分别是，当前元素、下标、当前遍历数组
 * @param   {any}      thisArg  可选，callback 函数执行是 this 的值
 * @returns {Array}    新数组，由 callback 内部返回值组成
 */
Array.prototype.myMap = function (callback) {
  // 保存当前处理数组
  var _thisArr = this;
  // 保存数组长度：用于遍历
  var _thisLen = _thisArr.length;
  // 获取第二个参数，用于改变 callback 函数内部 this 的值，默认 window
  var _thisArg = arguments[1] || window;
  // 保存返回的新数组
  var _newArr = [];
  // 拷贝每次遍历的元素
  var _item;
  // 每次函数返回结果保存，用于处理未返回数据不进入新数组
  var _res;

  for (var i = 0; i < _thisLen; i++) {
    // console.log(i);
    _item = deepClone(_thisArr[i]);
    _res = callback.apply(_thisArg, [_item, i, _thisArr]);
    _res && _newArr.push(_res);
  }

  return _newArr;
}

/**
 * @method 重写（filter）过滤数组元素
 * @param   {Function} callback 可传三个参数，从左到右分别是，当前元素、下标、当前遍历数组
 * @param   {any}      thisArg  可选，callback 函数执行是 this 的值
 * @returns {Array}    新数组，由 callback 内部返回值组成，条件是其返回值经过 Boolean 转换后为 true，最终保存的是其数组内的值
 */
Array.prototype.myFilter = function (cb) {
  var _arr = this;
  var _len = _arr.length;
  var _this = arguments[1] || window;
  var _newArr = [];
  var _item;

  for (var i = 0; i < _len; i++) {
    _item = deepClone(_arr[i]);
    cb.apply(_this, [_item, i, _arr]) && _newArr.push(_item);
  }

  return _newArr;
}

/**
 * @method 重写（every）测试一个空数组时，一切情况下返回（true）
 * @param   {Function} callback 可传三个参数，从左到右分别是，当前元素、下标、当前遍历数组
 * @param   {any}      thisArg  可选，callback 函数执行是 this 的值
 * @returns {Boolean}  由 callback 内部返回值决定，如果所有返回值的 Boolean 结果都为 true，那么返回 true，否则返回 false
 */
Array.prototype.myEvery = function (cb) {
  var _arr = this;
  var _this = arguments[1] || window;
  var _len = _arr.length;
  var _flag = true;

  for (var i = 0; i < _len; i++) {
    // TODO: 没考虑到的事情，因为只要返回 false，我就不需要继续遍历了才对，我第一次写的时候疏忽了，没把循环 break 掉
    // console.log(i);
    _flag = cb.apply(_this, [_arr[i], i, _arr]);
    if (!_flag) { break; }
  }

  return _flag;
}

/**
 * @method 重写（some）测试一个空数组时，一切情况下返回（false）
 * @param   {Function} callback 可传三个参数，从左到右分别是，当前元素、下标、当前遍历数组
 * @param   {any}      thisArg  可选，callback 函数执行是 this 的值
 * @returns {Boolean}  由 callback 内部返回值决定，若所有值返回 false，那么结果返回 false，否则返回 true
 */
Array.prototype.mySome = function (cb) {
  var _arr = this;
  var _len = _arr.length;
  var _this = arguments[1] || window;
  var _flag = false;

  for (var i = 0; i < _len; i++) {
    _flag = cb.apply(_this, [_arr[i], i, _arr]);
    if (_flag) { break };
  }

  return _flag;
}

/**
 * @method 重写（reduce）累加
 * @param   {Function} callback    可传四个参数，从左到右分别是，上一次 callback 的返回值（第二个参数传递了的话第一次初始值就是第二个参数，否则默认为数组的第一个元素），当前元素、下标、当前遍历数组
 * @param   {Number}   initValue   初始化值
 * @param   {any}      thisArg     改变 callback 中 this 指向
 * @returns {any}      最终返回一个 prev 的集合
 */
Array.prototype.myReduce = function (cb, initValue) {
  var _arr = this;
  var _len = _arr.length;
  var _prev = initValue;
  var _arg3 = arguments[2] || window;
  var _item;

  for (var i = 0; i < _len; i++) {
    _item = deepClone(_arr[i]);
    _prev = cb.apply(_arg3, [_prev, _item, i, _arr]);
  }

  return _prev;
}

// 使用方式与 reduce 一致，唯一不同的是它是从数组的最后一个到第一个元素进行遍历
Array.prototype.myReduceRight = function (cb, initValue) {
  var _arr = this;
  var _len = _arr.length;
  var _prev = initValue;
  var _arg3 = arguments[2] || window;
  var _item;

  for (var i = _len - 1; i >= 0; i--) {
    _item = deepClone(_arr[i]);
    _prev = cb.apply(_arg3, [_prev, _item, i, _arr]);
  }

  return _prev;
}